#include <QtGui>
#include <QtOpenGL>

#include <gl/GLU.h>

#define _USE_MATH_DEFINES
#include <math.h>

#include "pglwidget.h"

PGLWidget::PGLWidget(QWidget *parent)
	: QGLWidget(QGLFormat(QGL::SampleBuffers), parent)
{
	xRot = 0;
	yRot = 0;

	zCam = -10;
}

PGLWidget::~PGLWidget()
{
}

QSize PGLWidget::minimumSizeHint() const
{
	return QSize(50, 50);
}

QSize PGLWidget::sizeHint() const
{
	return QSize(400, 400);
}

void PGLWidget::initializeGL()
{
	clgl = new CLGL();
	clgl->resize(256,256);

	initializeScene();
	initializePipeline();
}

void PGLWidget::initializeScene()
{
	// set clear color
	cl_uchar4 black = {0,0,0,255};
	clgl->setClearColor(black);

	// Create pyramid vertex buffer
	vector<pglNormalVertex> pyramidBuffer;

	// front-facing triangle
	pglVector frontNormal(0,1,1,1);
	pyramidBuffer.push_back(pglNormalVertex(frontNormal,pglVector(0,1,0)));
	pyramidBuffer.push_back(pglNormalVertex(frontNormal,pglVector(-1,-1,-1)));
	pyramidBuffer.push_back(pglNormalVertex(frontNormal,pglVector(1,-1,-1)));

	// back-facing triangle
	pglVector backNormal(0,1,-1,1);
	pyramidBuffer.push_back(pglNormalVertex(backNormal,pglVector(0,1,0)));
	pyramidBuffer.push_back(pglNormalVertex(backNormal,pglVector(1,-1,1)));
	pyramidBuffer.push_back(pglNormalVertex(backNormal,pglVector(-1,-1,1)));

	// left-facing triangle
	pglVector leftNormal(-1,1,0,1);
	pyramidBuffer.push_back(pglNormalVertex(leftNormal,pglVector(0,1,0)));
	pyramidBuffer.push_back(pglNormalVertex(leftNormal,pglVector(-1,-1,1)));
	pyramidBuffer.push_back(pglNormalVertex(leftNormal,pglVector(-1,-1,-1)));

	// right-facing triangle
	pglVector rightNormal(1,1,0,1);
	pyramidBuffer.push_back(pglNormalVertex(rightNormal,pglVector(0,1,0)));
	pyramidBuffer.push_back(pglNormalVertex(rightNormal,pglVector(1,-1,-1)));
	pyramidBuffer.push_back(pglNormalVertex(rightNormal,pglVector(1,-1,1)));

	// bottom quad
	pglVector bottomNormal(0,-1,0,1);
	pyramidBuffer.push_back(pglNormalVertex(bottomNormal,pglVector(-1,-1,-1)));
	pyramidBuffer.push_back(pglNormalVertex(bottomNormal,pglVector(-1,-1,1)));
	pyramidBuffer.push_back(pglNormalVertex(bottomNormal,pglVector(1,-1,1)));

	pyramidBuffer.push_back(pglNormalVertex(bottomNormal,pglVector(1,-1,1)));
	pyramidBuffer.push_back(pglNormalVertex(bottomNormal,pglVector(1,-1,-1)));
	pyramidBuffer.push_back(pglNormalVertex(bottomNormal,pglVector(-1,-1,-1)));

	// Get number of vertices
	pglPyramidNumVertices = pyramidBuffer.size();

	// Create vertex buffer
	pglPyramidVertexBufferObj = clgl->createMemObject(&pyramidBuffer[0],pglPyramidNumVertices*sizeof(pglNormalVertex));

	// Output vertex buffer
	pglPyramidOutVertexBufferObj = clgl->createMemObject(NULL,pglPyramidNumVertices*sizeof(pglNormalVertex));

	// Create matrix
	pglWorldViewProjectionMatrixObj = clgl->createMemObject(NULL,sizeof(pglMatrix));
	
	// Number of matrices
	pglNumMatrices = 1;
}

void PGLWidget::initializePipeline()
{
	// set non-default (custom) pipeline arguments
	clgl->vertexTransformState.arguments[clgl->vertexTransformState.argumentNames["vertices"]].argptr = &(pglPyramidVertexBufferObj.memObject);
	clgl->vertexTransformState.arguments[clgl->vertexTransformState.argumentNames["outVertices"]].argptr = &(pglPyramidOutVertexBufferObj.memObject);
	clgl->vertexTransformState.arguments[clgl->vertexTransformState.argumentNames["matrices"]].argptr = &(pglWorldViewProjectionMatrixObj.memObject);
	clgl->vertexTransformState.arguments[clgl->vertexTransformState.argumentNames["numMatrices"]].argptr = &(pglNumMatrices);

	clgl->vertexTransformState.dimensions = 1;
	clgl->vertexTransformState.globalWorkSize = new cl_uint[1];
	clgl->vertexTransformState.globalWorkSize[0] = pglPyramidNumVertices;
	clgl->vertexTransformState.localWorkSize = NULL;

	clgl->rasterState.arguments[clgl->rasterState.argumentNames["vertices"]].argptr = &(pglPyramidOutVertexBufferObj.memObject);

	clgl->rasterState.dimensions = 1;
	clgl->rasterState.globalWorkSize = new cl_uint[1];
	clgl->rasterState.globalWorkSize[0] = pglPyramidNumVertices;
	clgl->rasterState.localWorkSize = NULL;

}

void PGLWidget::paintGL()
{
	// World matrix
	pglMatrix worldRotateX;
	pglMatrix worldRotateY;
	matrixLoadRotationAboutX(&worldRotateX,-(xRot/360.0f)*2.0f*M_PI);
	matrixLoadRotationAboutY(&worldRotateY,(yRot/360.0f)*2.0f*M_PI);

	// View matrix
	pglVector eye(0,0,zCam);
	pglVector at(0,0,0);
	pglVector up(0,1,0);
	matrixLoadLookAt(&pglViewMatrix,eye,at,up);

	// World*View*Projection matrix
	clgl->remapMemObject(pglWorldViewProjectionMatrixObj);
	pglMatrix worldViewProjectionMatrix = worldRotateY * worldRotateX * pglViewMatrix * pglProjectionMatrix;
	memcpy(pglWorldViewProjectionMatrixObj.hostPtr,&worldViewProjectionMatrix,sizeof(pglMatrix));

	// Unmap buffers
	clgl->unmapMemObject(pglWorldViewProjectionMatrixObj);
	clgl->unmapMemObject(pglPyramidVertexBufferObj);
	clgl->unmapMemObject(pglPyramidOutVertexBufferObj);

	// draw pyramid vertex buffer
	clgl->setDefaultPipelineArgs();

	// run the pipeline!
	clgl->runPipeline();

	// Unmap buffers
	clgl->remapMemObject(pglWorldViewProjectionMatrixObj);
	clgl->remapMemObject(pglPyramidVertexBufferObj);
	clgl->remapMemObject(pglPyramidOutVertexBufferObj);

	cl_float* inPtr = (cl_float*)(pglPyramidVertexBufferObj.hostPtr);
	cl_float* outPtr = (cl_float*)(pglPyramidOutVertexBufferObj.hostPtr);

	clgl->drawPixelsToGl();
}

void PGLWidget::resizeGL(int width, int height)
{
	clgl->resize(width,height);
	clgl->setViewport( 0, 0, width, height );
	matrixLoadPerspectiveProjection(&pglProjectionMatrix, M_PI_4,(float)width/(float)height,0.1f, 100.0f);
	matrixLoadViewportTransform(&pglViewportMatrix,0,0,width,height,0,1);
}

void PGLWidget::mousePressEvent(QMouseEvent *event)
{
	lastPos = event->pos();
}

void PGLWidget::mouseMoveEvent(QMouseEvent *event)
{
	int dx = event->x() - lastPos.x();
	int dy = event->y() - lastPos.y();

	if (event->buttons() & Qt::LeftButton)
	{
		yRot += (float)dx;
		xRot -= (float)dy;

		emit rotationSet(xRot,yRot);

		repaint();
	}
	else if (event->buttons() & Qt::RightButton)
	{

	}
	lastPos = event->pos();
}

void PGLWidget::wheelEvent(QWheelEvent *event)
{
	int dw = event->delta();

	zCam += (float)dw*0.01f;

	emit zoomSet(zCam);

	repaint();
}